#pragma once

#include <windows.h>
#include <memory>
#include <stdint.h>

class Keyboard
{
public:
	Keyboard() noexcept(false);
	Keyboard(Keyboard&& moveFrom) noexcept;
	Keyboard& operator= (Keyboard&& moveFrom) noexcept;

	Keyboard(Keyboard const&) = delete;
	Keyboard& operator=(Keyboard const&) = delete;

	virtual ~Keyboard();

	enum Keys
	{
		None = 0,

		Back = 0x8,
		Tab = 0x9,

		Enter = 0xd,

		Pause = 0x13,
		CapsLock = 0x14,
		Kana = 0x15,

		Kanji = 0x19,

		Escape = 0x1b,
		ImeConvert = 0x1c,
		ImeNoConvert = 0x1d,

		Space = 0x20,
		PageUp = 0x21,
		PageDown = 0x22,
		End = 0x23,
		Home = 0x24,
		Left = 0x25,
		Up = 0x26,
		Right = 0x27,
		Down = 0x28,
		Select = 0x29,
		Print = 0x2a,
		Execute = 0x2b,
		PrintScreen = 0x2c,
		Insert = 0x2d,
		Delete = 0x2e,
		Help = 0x2f,
		D0 = 0x30,
		D1 = 0x31,
		D2 = 0x32,
		D3 = 0x33,
		D4 = 0x34,
		D5 = 0x35,
		D6 = 0x36,
		D7 = 0x37,
		D8 = 0x38,
		D9 = 0x39,

		A = 0x41,
		B = 0x42,
		C = 0x43,
		D = 0x44,
		E = 0x45,
		F = 0x46,
		G = 0x47,
		H = 0x48,
		I = 0x49,
		J = 0x4a,
		K = 0x4b,
		L = 0x4c,
		M = 0x4d,
		N = 0x4e,
		O = 0x4f,
		P = 0x50,
		Q = 0x51,
		R = 0x52,
		S = 0x53,
		T = 0x54,
		U = 0x55,
		V = 0x56,
		W = 0x57,
		X = 0x58,
		Y = 0x59,
		Z = 0x5a,
		LeftWindows = 0x5b,
		RightWindows = 0x5c,
		Apps = 0x5d,

		Sleep = 0x5f,
		NumPad0 = 0x60,
		NumPad1 = 0x61,
		NumPad2 = 0x62,
		NumPad3 = 0x63,
		NumPad4 = 0x64,
		NumPad5 = 0x65,
		NumPad6 = 0x66,
		NumPad7 = 0x67,
		NumPad8 = 0x68,
		NumPad9 = 0x69,
		Multiply = 0x6a,
		Add = 0x6b,
		Separator = 0x6c,
		Subtract = 0x6d,

		Decimal = 0x6e,
		Divide = 0x6f,
		F1 = 0x70,
		F2 = 0x71,
		F3 = 0x72,
		F4 = 0x73,
		F5 = 0x74,
		F6 = 0x75,
		F7 = 0x76,
		F8 = 0x77,
		F9 = 0x78,
		F10 = 0x79,
		F11 = 0x7a,
		F12 = 0x7b,
		F13 = 0x7c,
		F14 = 0x7d,
		F15 = 0x7e,
		F16 = 0x7f,
		F17 = 0x80,
		F18 = 0x81,
		F19 = 0x82,
		F20 = 0x83,
		F21 = 0x84,
		F22 = 0x85,
		F23 = 0x86,
		F24 = 0x87,

		NumLock = 0x90,
		Scroll = 0x91,

		LeftShift = 0xa0,
		RightShift = 0xa1,
		LeftControl = 0xa2,
		RightControl = 0xa3,
		LeftAlt = 0xa4,
		RightAlt = 0xa5,
		BrowserBack = 0xa6,
		BrowserForward = 0xa7,
		BrowserRefresh = 0xa8,
		BrowserStop = 0xa9,
		BrowserSearch = 0xaa,
		BrowserFavorites = 0xab,
		BrowserHome = 0xac,
		VolumeMute = 0xad,
		VolumeDown = 0xae,
		VolumeUp = 0xaf,
		MediaNextTrack = 0xb0,
		MediaPreviousTrack = 0xb1,
		MediaStop = 0xb2,
		MediaPlayPause = 0xb3,
		LaunchMail = 0xb4,
		SelectMedia = 0xb5,
		LaunchApplication1 = 0xb6,
		LaunchApplication2 = 0xb7,

		OemSemicolon = 0xba,
		OemPlus = 0xbb,
		OemComma = 0xbc,
		OemMinus = 0xbd,
		OemPeriod = 0xbe,
		OemQuestion = 0xbf,
		OemTilde = 0xc0,

		OemOpenBrackets = 0xdb,
		OemPipe = 0xdc,
		OemCloseBrackets = 0xdd,
		OemQuotes = 0xde,
		Oem8 = 0xdf,

		OemBackslash = 0xe2,

		ProcessKey = 0xe5,

		OemCopy = 0xf2,
		OemAuto = 0xf3,
		OemEnlW = 0xf4,

		Attn = 0xf6,
		Crsel = 0xf7,
		Exsel = 0xf8,
		EraseEof = 0xf9,
		Play = 0xfa,
		Zoom = 0xfb,

		Pa1 = 0xfd,
		OemClear = 0xfe,
	};

	struct State
	{
		bool Reserved0 : 8;
		bool Back : 1;              // VK_BACK, 0x8
		bool Tab : 1;               // VK_TAB, 0x9
		bool Reserved1 : 3;
		bool Enter : 1;             // VK_RETURN, 0xD
		bool Reserved2 : 2;
		bool Reserved3 : 3;
		bool Pause : 1;             // VK_PAUSE, 0x13
		bool CapsLock : 1;          // VK_CAPITAL, 0x14
		bool Kana : 1;              // VK_KANA, 0x15
		bool Reserved4 : 2;
		bool Reserved5 : 1;
		bool Kanji : 1;             // VK_KANJI, 0x19
		bool Reserved6 : 1;
		bool Escape : 1;            // VK_ESCAPE, 0x1B
		bool ImeConvert : 1;        // VK_CONVERT, 0x1C
		bool ImeNoConvert : 1;      // VK_NONCONVERT, 0x1D
		bool Reserved7 : 2;
		bool Space : 1;             // VK_SPACE, 0x20
		bool PageUp : 1;            // VK_PRIOR, 0x21
		bool PageDown : 1;          // VK_NEXT, 0x22
		bool End : 1;               // VK_END, 0x23
		bool Home : 1;              // VK_HOME, 0x24
		bool Left : 1;              // VK_LEFT, 0x25
		bool Up : 1;                // VK_UP, 0x26
		bool Right : 1;             // VK_RIGHT, 0x27
		bool Down : 1;              // VK_DOWN, 0x28
		bool Select : 1;            // VK_SELECT, 0x29
		bool Print : 1;             // VK_PRINT, 0x2A
		bool Execute : 1;           // VK_EXECUTE, 0x2B
		bool PrintScreen : 1;       // VK_SNAPSHOT, 0x2C
		bool Insert : 1;            // VK_INSERT, 0x2D
		bool Delete : 1;            // VK_DELETE, 0x2E
		bool Help : 1;              // VK_HELP, 0x2F
		bool D0 : 1;                // 0x30
		bool D1 : 1;                // 0x31
		bool D2 : 1;                // 0x32
		bool D3 : 1;                // 0x33
		bool D4 : 1;                // 0x34
		bool D5 : 1;                // 0x35
		bool D6 : 1;                // 0x36
		bool D7 : 1;                // 0x37
		bool D8 : 1;                // 0x38
		bool D9 : 1;                // 0x39
		bool Reserved8 : 6;
		bool Reserved9 : 1;
		bool A : 1;                 // 0x41
		bool B : 1;                 // 0x42
		bool C : 1;                 // 0x43
		bool D : 1;                 // 0x44
		bool E : 1;                 // 0x45
		bool F : 1;                 // 0x46
		bool G : 1;                 // 0x47
		bool H : 1;                 // 0x48
		bool I : 1;                 // 0x49
		bool J : 1;                 // 0x4A
		bool K : 1;                 // 0x4B
		bool L : 1;                 // 0x4C
		bool M : 1;                 // 0x4D
		bool N : 1;                 // 0x4E
		bool O : 1;                 // 0x4F
		bool P : 1;                 // 0x50
		bool Q : 1;                 // 0x51
		bool R : 1;                 // 0x52
		bool S : 1;                 // 0x53
		bool T : 1;                 // 0x54
		bool U : 1;                 // 0x55
		bool V : 1;                 // 0x56
		bool W : 1;                 // 0x57
		bool X : 1;                 // 0x58
		bool Y : 1;                 // 0x59
		bool Z : 1;                 // 0x5A
		bool LeftWindows : 1;       // VK_LWIN, 0x5B
		bool RightWindows : 1;      // VK_RWIN, 0x5C
		bool Apps : 1;              // VK_APPS, 0x5D
		bool Reserved10 : 1;
		bool Sleep : 1;             // VK_SLEEP, 0x5F
		bool NumPad0 : 1;           // VK_NUMPAD0, 0x60
		bool NumPad1 : 1;           // VK_NUMPAD1, 0x61
		bool NumPad2 : 1;           // VK_NUMPAD2, 0x62
		bool NumPad3 : 1;           // VK_NUMPAD3, 0x63
		bool NumPad4 : 1;           // VK_NUMPAD4, 0x64
		bool NumPad5 : 1;           // VK_NUMPAD5, 0x65
		bool NumPad6 : 1;           // VK_NUMPAD6, 0x66
		bool NumPad7 : 1;           // VK_NUMPAD7, 0x67
		bool NumPad8 : 1;           // VK_NUMPAD8, 0x68
		bool NumPad9 : 1;           // VK_NUMPAD9, 0x69
		bool Multiply : 1;          // VK_MULTIPLY, 0x6A
		bool Add : 1;               // VK_ADD, 0x6B
		bool Separator : 1;         // VK_SEPARATOR, 0x6C
		bool Subtract : 1;          // VK_SUBTRACT, 0x6D
		bool Decimal : 1;           // VK_DECIMANL, 0x6E
		bool Divide : 1;            // VK_DIVIDE, 0x6F
		bool F1 : 1;                // VK_F1, 0x70
		bool F2 : 1;                // VK_F2, 0x71
		bool F3 : 1;                // VK_F3, 0x72
		bool F4 : 1;                // VK_F4, 0x73
		bool F5 : 1;                // VK_F5, 0x74
		bool F6 : 1;                // VK_F6, 0x75
		bool F7 : 1;                // VK_F7, 0x76
		bool F8 : 1;                // VK_F8, 0x77
		bool F9 : 1;                // VK_F9, 0x78
		bool F10 : 1;               // VK_F10, 0x79
		bool F11 : 1;               // VK_F11, 0x7A
		bool F12 : 1;               // VK_F12, 0x7B
		bool F13 : 1;               // VK_F13, 0x7C
		bool F14 : 1;               // VK_F14, 0x7D
		bool F15 : 1;               // VK_F15, 0x7E
		bool F16 : 1;               // VK_F16, 0x7F
		bool F17 : 1;               // VK_F17, 0x80
		bool F18 : 1;               // VK_F18, 0x81
		bool F19 : 1;               // VK_F19, 0x82
		bool F20 : 1;               // VK_F20, 0x83
		bool F21 : 1;               // VK_F21, 0x84
		bool F22 : 1;               // VK_F22, 0x85
		bool F23 : 1;               // VK_F23, 0x86
		bool F24 : 1;               // VK_F24, 0x87
		bool Reserved11 : 8;
		bool NumLock : 1;           // VK_NUMLOCK, 0x90
		bool Scroll : 1;            // VK_SCROLL, 0x91
		bool Reserved12 : 6;
		bool Reserved13 : 8;
		bool LeftShift : 1;         // VK_LSHIFT, 0xA0
		bool RightShift : 1;        // VK_RSHIFT, 0xA1
		bool LeftControl : 1;       // VK_LCONTROL, 0xA2
		bool RightControl : 1;      // VK_RCONTROL, 0xA3
		bool LeftAlt : 1;           // VK_LMENU, 0xA4
		bool RightAlt : 1;          // VK_RMENU, 0xA5
		bool BrowserBack : 1;       // VK_BROWSER_BACK, 0xA6
		bool BrowserForward : 1;    // VK_BROWSER_FORWARD, 0xA7
		bool BrowserRefresh : 1;    // VK_BROWSER_REFRESH, 0xA8
		bool BrowserStop : 1;       // VK_BROWSER_STOP, 0xA9
		bool BrowserSearch : 1;     // VK_BROWSER_SEARCH, 0xAA
		bool BrowserFavorites : 1;  // VK_BROWSER_FAVORITES, 0xAB
		bool BrowserHome : 1;       // VK_BROWSER_HOME, 0xAC
		bool VolumeMute : 1;        // VK_VOLUME_MUTE, 0xAD
		bool VolumeDown : 1;        // VK_VOLUME_DOWN, 0xAE
		bool VolumeUp : 1;          // VK_VOLUME_UP, 0xAF
		bool MediaNextTrack : 1;    // VK_MEDIA_NEXT_TRACK, 0xB0
		bool MediaPreviousTrack : 1;// VK_MEDIA_PREV_TRACK, 0xB1
		bool MediaStop : 1;         // VK_MEDIA_STOP, 0xB2
		bool MediaPlayPause : 1;    // VK_MEDIA_PLAY_PAUSE, 0xB3
		bool LaunchMail : 1;        // VK_LAUNCH_MAIL, 0xB4
		bool SelectMedia : 1;       // VK_LAUNCH_MEDIA_SELECT, 0xB5
		bool LaunchApplication1 : 1;// VK_LAUNCH_APP1, 0xB6
		bool LaunchApplication2 : 1;// VK_LAUNCH_APP2, 0xB7
		bool Reserved14 : 2;
		bool OemSemicolon : 1;      // VK_OEM_1, 0xBA
		bool OemPlus : 1;           // VK_OEM_PLUS, 0xBB
		bool OemComma : 1;          // VK_OEM_COMMA, 0xBC
		bool OemMinus : 1;          // VK_OEM_MINUS, 0xBD
		bool OemPeriod : 1;         // VK_OEM_PERIOD, 0xBE
		bool OemQuestion : 1;       // VK_OEM_2, 0xBF
		bool OemTilde : 1;          // VK_OEM_3, 0xC0
		bool Reserved15 : 7;
		bool Reserved16 : 8;
		bool Reserved17 : 8;
		bool Reserved18 : 3;
		bool OemOpenBrackets : 1;   // VK_OEM_4, 0xDB
		bool OemPipe : 1;           // VK_OEM_5, 0xDC
		bool OemCloseBrackets : 1;  // VK_OEM_6, 0xDD
		bool OemQuotes : 1;         // VK_OEM_7, 0xDE
		bool Oem8 : 1;              // VK_OEM_8, 0xDF
		bool Reserved19 : 2;
		bool OemBackslash : 1;      // VK_OEM_102, 0xE2
		bool Reserved20 : 2;
		bool ProcessKey : 1;        // VK_PROCESSKEY, 0xE5
		bool Reserved21 : 2;
		bool Reserved22 : 8;
		bool Reserved23 : 2;
		bool OemCopy : 1;           // 0XF2
		bool OemAuto : 1;           // 0xF3
		bool OemEnlW : 1;           // 0xF4
		bool Reserved24 : 1;
		bool Attn : 1;              // VK_ATTN, 0xF6
		bool Crsel : 1;             // VK_CRSEL, 0xF7
		bool Exsel : 1;             // VK_EXSEL, 0xF8
		bool EraseEof : 1;          // VK_EREOF, 0xF9
		bool Play : 1;              // VK_PLAY, 0xFA
		bool Zoom : 1;              // VK_ZOOM, 0xFB
		bool Reserved25 : 1;
		bool Pa1 : 1;               // VK_PA1, 0xFD
		bool OemClear : 1;          // VK_OEM_CLEAR, 0xFE
		bool Reserved26 : 1;

		bool __cdecl IsKeyDown(Keys key) const
		{
			if (key >= 0 && key <= 0xfe)
			{
				auto ptr = reinterpret_cast<const uint32_t*>(this);
				unsigned int bf = 1u << (key & 0x1f);
				return (ptr[(key >> 5)] & bf) != 0;
			}
			return false;
		}

		bool __cdecl IsKeyUp(Keys key) const
		{
			if (key >= 0 && key <= 0xfe)
			{
				auto ptr = reinterpret_cast<const uint32_t*>(this);
				unsigned int bf = 1u << (key & 0x1f);
				return (ptr[(key >> 5)] & bf) == 0;
			}
			return false;
		}
	};

	class KeyboardStateTracker
	{
	public:
		State released;
		State pressed;

#pragma prefast(suppress: 26495, "Reset() performs the initialization")
		KeyboardStateTracker() noexcept { Reset(); }

		void __cdecl Update(const State& state);

		void __cdecl Reset() noexcept;

		bool __cdecl IsKeyPressed(Keys key) const { return pressed.IsKeyDown(key); }
		bool __cdecl IsKeyReleased(Keys key) const { return released.IsKeyDown(key); }

		State __cdecl GetLastState() const { return lastState; }

	public:
		State lastState;
	};

	// Retrieve the current state of the keyboard
	State __cdecl GetState() const;

	// Reset the keyboard state
	void __cdecl Reset();

	// Feature detection
	bool __cdecl IsConnected() const;

#if !defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP) && defined(WM_USER)
	static void __cdecl ProcessMessage(UINT message, WPARAM wParam, LPARAM lParam);
#endif

	// Singleton
	static Keyboard& __cdecl Get();

private:
	// Private implementation.
	class Impl;

	std::unique_ptr<Impl> pImpl;
};